package com.gitee.qdbp.jdbc.support;

import java.util.List;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterTools;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.jdbc.api.SqlBoot;
import com.gitee.qdbp.jdbc.biz.SqlBootImpl;
import com.gitee.qdbp.jdbc.biz.SqlBufferJdbcTemplate;
import com.gitee.qdbp.jdbc.model.DbVersion;
import com.gitee.qdbp.jdbc.plugins.DbPluginHelper;
import com.gitee.qdbp.jdbc.plugins.SqlDialect;
import com.gitee.qdbp.jdbc.result.RowToMapMapper;
import com.gitee.qdbp.jdbc.result.SimpleRowToMapMapper;
import com.gitee.qdbp.jdbc.sql.SqlBuffer;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * NamedParameterJdbcTemplate的代理类<br>
 * 主要用于旧项目改造, 同时使用SqlBufferJdbcOperations和NamedParameterJdbcOperations会导致日志输出两次<br>
 * SqlBufferJdbcTemplate输出的是可执行SQL, JdbcTemplate输出的是带?的SQL<br>
 * 因此: 先关闭JdbcTemplate的日志;<br>
 * 再使用NamedParameterQdbcTemplate替换NamedParameterJdbcTemplate, 将SQL转换为SqlBuffer执行
 *
 * @author zhaohuihua
 * @version 20201208
 */
public class NamedParameterQdbcTemplate implements NamedParameterJdbcOperations {

    private final SqlBufferJdbcTemplate jdbc;
    private final NamedParameterTools namedParameterTools;

    public NamedParameterQdbcTemplate(SqlBufferJdbcTemplate jdbc) {
        this(jdbc, newSimpleRowToMapMapper(jdbc.plugins()));
    }

    public NamedParameterQdbcTemplate(SqlBufferJdbcTemplate jdbc, RowToMapMapper rowToMapMapper) {
        VerifyTools.requireNonNull(jdbc, "jdbc");
        VerifyTools.requireNonNull(rowToMapMapper, "rowToMapMapper");
        this.jdbc = new MapSqlBufferJdbcTemplate(jdbc, rowToMapMapper);
        SqlBoot sqlBoot = new SqlBootImpl(jdbc.sqlDialect(), jdbc.plugins());
        this.namedParameterTools = new NamedParameterTools(sqlBoot);
    }

    private static SimpleRowToMapMapper newSimpleRowToMapMapper(DbPluginHelper plugins) {
        SimpleRowToMapMapper mapper = new SimpleRowToMapMapper();
        mapper.setPlugins(plugins);
        return mapper;
    }

    @Override
    public JdbcOperations getJdbcOperations() {
        return this.jdbc.getJdbcOperations();
    }

    @Override
    public <T> T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback<T> action)
            throws DataAccessException {
        VerifyTools.requireNotBlank(sql, "sql");
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            T result = this.jdbc.execute(sb, action);
            return requiredSingleResult(result);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> T execute(String sql, Map<String, ?> paramMap, PreparedStatementCallback<T> action)
            throws DataAccessException {
        return this.execute(sql, new MapSqlParameterSource(paramMap), action);
    }

    @Override
    public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
        return this.execute(sql, EmptySqlParameterSource.INSTANCE, action);
    }

    @Override
    public <T> T query(String sql, SqlParameterSource paramSource, ResultSetExtractor<T> rse)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            T result = this.jdbc.query(sb, rse);
            return requiredSingleResult(result);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> T query(String sql, Map<String, ?> paramMap, ResultSetExtractor<T> rse) throws DataAccessException {
        return this.query(sql, new MapSqlParameterSource(paramMap), rse);
    }

    @Override
    public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
        return this.query(sql, EmptySqlParameterSource.INSTANCE, rse);
    }

    @Override
    public void query(String sql, SqlParameterSource paramSource, RowCallbackHandler rch) throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            this.jdbc.query(sb, rch);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public void query(String sql, Map<String, ?> paramMap, RowCallbackHandler rch) throws DataAccessException {
        this.query(sql, new MapSqlParameterSource(paramMap), rch);
    }

    @Override
    public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
        this.query(sql, EmptySqlParameterSource.INSTANCE, rch);
    }

    @Override
    public <T> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            return this.jdbc.query(sb, rowMapper);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) throws DataAccessException {
        return this.query(sql, new MapSqlParameterSource(paramMap), rowMapper);
    }

    @Override
    public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        return this.query(sql, EmptySqlParameterSource.INSTANCE, rowMapper);
    }

    @Override
    public <T> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            T result = this.jdbc.queryForObject(sb, rowMapper);
            return requiredSingleResult(result);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
            throws DataAccessException {
        return this.queryForObject(sql, new MapSqlParameterSource(paramMap), rowMapper);
    }

    @Override
    public <T> T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            T result = this.jdbc.queryForObject(sb, requiredType);
            return requiredSingleResult(result);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> T queryForObject(String sql, Map<String, ?> paramMap, Class<T> requiredType) throws DataAccessException {
        return this.queryForObject(sql, new MapSqlParameterSource(paramMap), requiredType);
    }

    @Override
    public Map<String, Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            Map<String, Object> result = this.jdbc.queryForMap(sb);
            return requiredSingleResult(result);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public Map<String, Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException {
        return this.queryForMap(sql, new MapSqlParameterSource(paramMap));
    }

    @Override
    public <T> List<T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            return this.jdbc.queryForList(sb, elementType);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public <T> List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType)
            throws DataAccessException {
        return this.queryForList(sql, new MapSqlParameterSource(paramMap), elementType);
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql, SqlParameterSource paramSource)
            throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            return this.jdbc.queryForList(sb);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public List<Map<String, Object>> queryForList(String sql, Map<String, ?> paramMap) throws DataAccessException {
        return this.queryForList(sql, new MapSqlParameterSource(paramMap));
    }

    @Override
    public SqlRowSet queryForRowSet(String sql, SqlParameterSource paramSource) throws DataAccessException {
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        try {
            return this.jdbc.queryForRowSet(sb);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public SqlRowSet queryForRowSet(String sql, Map<String, ?> paramMap) throws DataAccessException {
        return this.queryForRowSet(sql, new MapSqlParameterSource(paramMap));
    }

    @Override
    public int update(String sql, SqlParameterSource paramSource) throws DataAccessException {
        VerifyTools.requireNotBlank(sql, "sql");
        SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
        String lowerSql = sql.toLowerCase();
        try {
            if (lowerSql.contains("insert")) {
                return this.jdbc.insert(sb);
            } else if (lowerSql.contains("delete")) {
                return this.jdbc.delete(sb);
            } else {
                return this.jdbc.update(sb);
            }
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public int update(String sql, Map<String, ?> paramMap) throws DataAccessException {
        return this.update(sql, new MapSqlParameterSource(paramMap));
    }

    @Override
    public int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHolder)
            throws DataAccessException {
        VerifyTools.requireNotBlank(sql, "sql");
        String lowerSql = sql.toLowerCase();
        try {
            if (lowerSql.contains("insert")) {
                SqlBuffer sb = namedParameterTools.buildSqlBuffer(sql, paramSource);
                return this.jdbc.insert(sb, generatedKeyHolder);
            } else {
                NamedParameterJdbcOperations npjo = this.jdbc.getNamedParameterJdbcOperations();
                return npjo.update(sql, paramSource, generatedKeyHolder);
            }
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHolder, String[] keyColumnNames)
            throws DataAccessException {
        NamedParameterJdbcOperations npjdbc = this.jdbc.getNamedParameterJdbcOperations();
        try {
            return npjdbc.update(sql, paramSource, generatedKeyHolder, keyColumnNames);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public int[] batchUpdate(String sql, Map<String, ?>[] batchValues) {
        NamedParameterJdbcOperations npjdbc = this.jdbc.getNamedParameterJdbcOperations();
        try {
            return npjdbc.batchUpdate(sql, batchValues);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    @Override
    public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
        NamedParameterJdbcOperations npjdbc = this.jdbc.getNamedParameterJdbcOperations();
        try {
            return npjdbc.batchUpdate(sql, batchArgs);
        } catch (ServiceException e) {
            throw toDataAccessException(e);
        }
    }

    private <T> T requiredSingleResult(T object) {
        if (object == null) {
            throw new EmptyResultDataAccessException(1);
        }
        return object;
    }

    private RuntimeException toDataAccessException(ServiceException e) {
        if (e.getCause() instanceof DataAccessException) {
            return (DataAccessException) e.getCause();
        } else {
            return e;
        }
    }

    /** 自定义RowToMapMapper **/
    private static class MapSqlBufferJdbcTemplate extends SqlBufferJdbcTemplate {

        private final SqlBufferJdbcTemplate parent;
        private final RowToMapMapper rowToMapMapper;

        public MapSqlBufferJdbcTemplate(SqlBufferJdbcTemplate parent, RowToMapMapper rowToMapMapper) {
            super(parent);
            this.parent = parent;
            this.rowToMapMapper = rowToMapMapper;
        }

        @Override
        public DbVersion dbVersion() {
            return this.parent.dbVersion();
        }

        @Override
        public SqlDialect sqlDialect() {
            return this.parent.sqlDialect();
        }

        @Override
        protected RowMapper<Map<String, Object>> getRowToMapConverter() {
            return rowToMapMapper;
        }
    }
}
